package cz.apnetis.gwt.client;

import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.PushButton;
import com.google.gwt.user.client.ui.Widget;

public class ResultTableEditable<T, F extends TableModelEditable> extends ResultTable<T, F> {

  /** Flag: rows in table is editable */
  private boolean editable; public boolean isEditable() { return this.editable; } public void setEditable(final boolean editable) { this.editable = editable; }
  /** Flag: table is insertable */
  private boolean insertable; public boolean isInsertable() { return this.insertable; } public void setInsertable(final boolean insertable) { this.insertable = insertable; }
  /** Flag: rows in table is removable */
  private boolean removable; public boolean isRemovable() { return this.removable; } public void setRemovable(final boolean removable) { this.removable = removable; }

  private final Map<Integer, ValueChangeHandler> rowValueChangehandler = new HashMap<Integer, ValueChangeHandler>();
  private final Map<Integer, ValueChangeHandler> colValueChangehandler = new HashMap<Integer, ValueChangeHandler>();

  public ResultTableEditable(final F tableModel) {
    super(tableModel);
  }

  /** Return true if this table have actions buton for rows */
  private final boolean hasActionPanel() {
    return isEditable() || isInsertable() || isRemovable();
  }

  /** Count of visible column in table. This method is owervritable by child */
  @Override
  protected int columnCount() {
    int result = getColumnHeaders().size();
    if (hasActionPanel()) { result++; }
    return result;
  }

  /* (non-Javadoc)
   * @see cz.apnetis.gwt.client.ResultTable#fieldWidget(int, int)
   */
  @Override
  protected Widget fieldWidget(final int row, final int col) {
    final boolean isSum = (getTableModel() instanceof TableModelSum) && ((TableModelSum) getTableModel()).isSumRow(row);

    final int colShift;
    if (hasActionPanel()) {
      colShift = 1;
      if (!isSum && col == 0) {
        final ActionPanel panel = new ActionPanel(row);
        this.rowValueChangehandler.put(Integer.valueOf(row), panel);
        return panel;
      } else if (col == 0) { return null; }
    } else { colShift = 0; }

    final Widget result = super.fieldWidget(row, col - colShift);

    if (!isSum && (result instanceof HasValueChangeHandlers)) {
      ((HasValueChangeHandlers) result).addValueChangeHandler(new WidgetValueChangeHandlers(row, col - colShift));
    }

    return result;
  }

  /** Save changes on row to object, and maybe send to serve */
  public final void saveChangesOnRow(final int row) {
    final int colShift = hasActionPanel() ? 1 : 0;
    final int rowShift = getColumnHeaders() != null ? 1 : 0;

    for (int col = 0; col < getTable().getColumnCount() - colShift; col++) {
      final String column = getColumnHeaders().getVisibleColumns().get(col);
      getTableModel().setFieldValue(row, column,
          getTable().getWidget(row + rowShift, col + colShift));
    }

    getTableModel().saveRow(row);
  }

  /** Widget represented edit panel */
  private class ActionPanel extends Composite implements ValueChangeHandler {

    /** Row in which is panel inserted */
    private final int row; public int getRow() { return this.row; }

    /** Panel which contains button for action */
    private final FlowPanel panel = new FlowPanel();
    /** Button for cancel changes */
    private final PushButton cancelButton;
    /** Button for confirm changes */
    private final PushButton confirmButton;
    /** Button for confirm changes */
    private final PushButton removeButton;

    public void setEditMode(final boolean editMode) {
      this.confirmButton.setVisible(editMode);
      this.cancelButton.setVisible(editMode);
      this.removeButton.setVisible(!editMode);
    }

    public ActionPanel(final int row) {
      this.row = row;

      final Image cancelIcon = new Image(ActionIconImages.INSTANCE.icon_cancel());
      this.cancelButton = new PushButton(cancelIcon);

      this.cancelButton.addClickHandler(new ClickHandler() {
        @Override
        public void onClick(final ClickEvent event) {
          repaintRow(getRow());
          setEditMode(false);
        }
      });

      this.panel.add(this.cancelButton);

      final Image confirmIcon = new Image(ActionIconImages.INSTANCE.icon_ok());
      this.confirmButton = new PushButton(confirmIcon);

      this.confirmButton.addClickHandler(new ClickHandler() {
        @Override
        public void onClick(final ClickEvent event) {
          ResultTableEditable.this.saveChangesOnRow(getRow());
          setEditMode(false);
        }
      });

      this.panel.add(this.confirmButton);

      final Image removeIcon = new Image(ActionIconImages.INSTANCE.icon_remove());
      this.removeButton = new PushButton(removeIcon);

      this.removeButton.addClickHandler(new ClickHandler() {
        @Override
        public void onClick(final ClickEvent event) {
          getTableModel().removeRow(getRow());
          repaint();
        }
      });

      this.panel.add(this.removeButton);

      setEditMode(false);

      initWidget(this.panel);
    }

    @Override
    public void onValueChange(final ValueChangeEvent event) {
      setEditMode(true);
    }

    @Override
    public final void setVisible(final boolean visible) {
      super.setVisible(visible);
      this.panel.setVisible(visible);
    }
    @Override
    public final boolean isVisible() {
      return this.panel.isVisible();
    }
  }

  public class WidgetValueChangeHandlers implements ValueChangeHandler {
    private final int col;
    private final int row;
    public WidgetValueChangeHandlers(final int row, final int col) {
      this.row = row;
      this.col = col;
    }
    @Override
    public void onValueChange(final ValueChangeEvent event) {
      ValueChangeHandler<?> vch = ResultTableEditable.this.rowValueChangehandler.get(Integer.valueOf(this.row));
      if (vch != null) { vch.onValueChange(event); }
      vch = ResultTableEditable.this.colValueChangehandler.get(Integer.valueOf(this.col));
      if (vch != null) { vch.onValueChange(event); }
    }
  }

  /** Default implementation of result table model, which show every data
   * which are insert to it and for each field make EditableLabel
   * @author Lukas "benzin" Benda <a href="mailto:bendal@apnet.cz">bendal@apnet.cz</a>
   * @since Oct 2, 2010
   */
  public static abstract class AbstractTableModelEditable<T> extends AbstractTableModel<T> implements TableModelEditable {

    /** Values of list boxes */
    private Map<String, Map<String, String>> listBoxValues;

    @Override
    public Widget getFieldWidget(final int row, final String colName) {
      final Object value = getField(getValues().get(row), colName);
      if (value instanceof Date) {
        final DateEditBox result = new DateEditBox();
        result.setValue((Date) value);
        return result;
      }

      final EditableLabel<Object> result = new EditableLabel<Object>();
      if (getListBoxItems() != null) {
        final Map<String, String> items = getListBoxItems().get(colName);
        if (items != null) { result.addListItems(items); }
      }
      result.setValue(value);
      return result;
    }
    @Override
    public void setFieldValue(final int row, final String colName, final Widget widget) {
      setField(getValues().get(row), colName, ((HasValue<?>) widget).getValue());
    }

    /** Method which add list item value to column list. If column contains only list values. */
    public final void addListBoxItem(final String colName, final String text, final String value) {
      if (this.listBoxValues == null) { this.listBoxValues = new HashMap<String, Map<String,String>>(1); }
      Map<String, String> map = this.listBoxValues.get(colName);
      if (map == null) {
        map = new LinkedHashMap<String, String>();
        this.listBoxValues.put(colName, map);
      }
      map.put(value, text);
    }

    /** Method which set map of items for list box in specific column */
    public final void addListBoxItems(final String colName, final Map<String, String> items) {
      if (this.listBoxValues == null) { this.listBoxValues = new HashMap<String, Map<String,String>>(1); }
      this.listBoxValues.put(colName, items);
    }

    public final Map<String, Map<String, String>> getListBoxItems() { return this.listBoxValues; }

    /** Method which set changed value in string form to object
     * @param value which is changed
     * @param col column which is changed
     * @param newFieldValue new value which is change
     */
    protected abstract void setField(T value, String colName, Object newFieldValue);
  }
}
